/* Tencent is pleased to support the open source community by making Hippy available.
 * Copyright (C) 2018 THL A29 Limited, a Tencent company. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.tencent.mtt.hippy.views.list;

import static com.tencent.renderer.utils.EventUtils.EVENT_LIST_FOOTER_PULLING;
import static com.tencent.renderer.utils.EventUtils.EVENT_LIST_FOOTER_RELEASED;
import static com.tencent.renderer.utils.EventUtils.EVENT_LIST_HEADER_PULLING;
import static com.tencent.renderer.utils.EventUtils.EVENT_LIST_HEADER_RELEASED;
import static com.tencent.renderer.utils.EventUtils.EVENT_LIST_ITEM_APPEAR;
import static com.tencent.renderer.utils.EventUtils.EVENT_LIST_ITEM_DISAPPEAR;
import static com.tencent.renderer.utils.EventUtils.EVENT_LIST_ITEM_WILL_APPEAR;
import static com.tencent.renderer.utils.EventUtils.EVENT_LIST_ITEM_WILL_DISAPPEAR;

import android.os.SystemClock;
import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.tencent.mtt.hippy.common.HippyMap;
import com.tencent.mtt.hippy.uimanager.HippyViewBase;
import com.tencent.mtt.hippy.uimanager.HippyViewEvent;
import com.tencent.mtt.hippy.uimanager.NativeGestureDispatcher;
import com.tencent.mtt.hippy.uimanager.RenderManager;
import com.tencent.renderer.node.RenderNode;
import com.tencent.mtt.hippy.utils.LogUtils;
import com.tencent.mtt.hippy.utils.PixelUtil;
import com.tencent.mtt.hippy.views.refresh.HippyPullFooterView;
import com.tencent.mtt.hippy.views.refresh.HippyPullHeaderView;
import com.tencent.mtt.supportui.views.recyclerview.BaseLayoutManager;
import com.tencent.mtt.supportui.views.recyclerview.LinearLayoutManager;
import com.tencent.mtt.supportui.views.recyclerview.RecyclerAdapter;
import com.tencent.mtt.supportui.views.recyclerview.RecyclerView;
import com.tencent.mtt.supportui.views.recyclerview.RecyclerViewItem;
import com.tencent.renderer.NativeRender;
import com.tencent.renderer.NativeRenderContext;
import com.tencent.renderer.NativeRendererManager;

import android.content.Context;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;

import com.tencent.renderer.utils.EventUtils;

import java.util.HashMap;
import java.util.Map;

@SuppressWarnings({"deprecation", "unused"})
public class HippyListView extends RecyclerView implements HippyViewBase {

    public final static int REFRESH_STATE_IDLE = 0;
    public final static int REFRESH_STATE_LOADING = 1;
    protected int mHeaderRefreshState = REFRESH_STATE_IDLE;
    protected int mFooterRefreshState = REFRESH_STATE_IDLE;
    protected final boolean mEnableRefresh = true;

    private RecyclerAdapter mListAdapter;

    private NativeGestureDispatcher mGestureDispatcher;

    protected boolean mScrollBeginDragEventEnable = false;

    protected boolean mScrollEndDragEventEnable = false;

    protected boolean mMomentumScrollBeginEventEnable = false;

    protected boolean mMomentumScrollEndEventEnable = false;

    protected boolean mScrollEventEnable = true;

    protected boolean mScrollEnable = true;

    protected boolean mExposureEventEnable = false;

    private float touchDownY;
    private float touchDownX;
    private int touchSlop;
    private int initialContentOffset = 0;
    private boolean hasCompleteFirstBatch = false;

    protected int mScrollEventThrottle = 400;  // 400ms最多回调一次
    protected int mLastOffsetX = Integer.MIN_VALUE;
    protected int mLastOffsetY = Integer.MIN_VALUE;
    protected long mLastScrollEventTimeStamp = -1;
    protected boolean mHasUnsentScrollEvent;

    private boolean mHasRemovePreDraw = false;
    private ViewTreeObserver.OnPreDrawListener mPreDrawListener = null;
    private ViewTreeObserver mViewTreeObserver = null;
    private OnInitialListReadyEvent mOnInitialListReadyEvent;

    private OnScrollDragStartedEvent mOnScrollDragStartedEvent;
    private OnScrollDragEndedEvent mOnScrollDragEndedEvent;
    private OnScrollFlingStartedEvent mOnScrollFlingStartedEvent;
    private OnScrollFlingEndedEvent mOnScrollFlingEndedEvent;
    private OnScrollEvent mOnScrollEvent;
    private NativeRender nativeRenderer;

    private void init(Context context, int orientation) {
        if (context instanceof NativeRenderContext) {
            int instanceId = ((NativeRenderContext) context).getInstanceId();
            nativeRenderer = NativeRendererManager.getNativeRenderer(instanceId);
        }
        this.setLayoutManager(new LinearLayoutManager(context, orientation, false));
        setRepeatableSuspensionMode(false);
        mListAdapter = createAdapter(this, nativeRenderer);
        setAdapter(mListAdapter);

        final ViewConfiguration configuration = ViewConfiguration.get(context);
        touchSlop = configuration.getScaledTouchSlop();
    }

    public HippyListView(Context context, int orientation) {
        super(context);
        init(context, orientation);
    }

    public HippyListView(Context context) {
        super(context);
        init(context, BaseLayoutManager.VERTICAL);
    }

    protected RecyclerAdapter createAdapter(RecyclerView hippyRecyclerView,
            NativeRender nativeRenderer) {
        return new HippyListAdapter(hippyRecyclerView, nativeRenderer);
    }

    @Override
    public NativeGestureDispatcher getGestureDispatcher() {
        return mGestureDispatcher;
    }

    @Override
    public void setGestureDispatcher(NativeGestureDispatcher dispatcher) {
        this.mGestureDispatcher = dispatcher;
    }

    @Override
    public boolean onTouchEvent(MotionEvent motionEvent) {
        if (!mScrollEnable) {
            return false;
        }
        return super.onTouchEvent(motionEvent);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent motionEvent) {
        if (!mScrollEnable) {
            return false;
        }

        if (mLayout.canScrollVertically()) {
            int action = motionEvent.getAction();
            float y = motionEvent.getY();
            float x = motionEvent.getX();
            switch (action) {
                case MotionEvent.ACTION_DOWN: {
                    touchDownY = y;
                    touchDownX = x;
                    break;
                }
                case MotionEvent.ACTION_MOVE: {
                    if (Math.abs(x - touchDownX) / Math.abs(y - touchDownY) > 1
                            && Math.abs(x - touchDownX) > touchSlop) {
                        return false;
                    }
                    break;
                }
            }
        }

        return super.onInterceptTouchEvent(motionEvent);
    }


    public void setListData() {
        LogUtils.d("hippylistview", "setListData");
        mListAdapter.notifyDataSetChanged();
        dispatchLayout();
        if (mExposureEventEnable) {
            dispatchExposureEvent();
        }
        if (!hasCompleteFirstBatch && getChildCount() > 0) {
            if (initialContentOffset > 0) {
                scrollToInitContentOffset();
            }
            hasCompleteFirstBatch = true;
        }
    }

    private void scrollToInitContentOffset() {
        int position = 0;
        int itemHeight = 0;
        if (mListAdapter == null) {
            return;
        }

        int itemCount = mListAdapter.getItemCount();
        for (; itemHeight < initialContentOffset && position < itemCount; position++) {
            itemHeight += mListAdapter.getItemHeight(position);
        }

        final int lastIndex = itemCount - 1;
        int offset = itemHeight - initialContentOffset;

        //设置的offset超过边界，跳转到最后一个Item上去
        if (position >= lastIndex) {
            position = lastIndex;
            //不能划出内容高度
            if (offset < 0) {
                offset = 0;
            }
        }

        scrollToPosition(position, offset);
    }

    public void setInitialContentOffset(int offset) {
        initialContentOffset = offset;
    }

    public void setScrollBeginDragEventEnable(boolean enable) {
        mScrollBeginDragEventEnable = enable;
    }

    public void setScrollEndDragEventEnable(boolean enable) {
        mScrollEndDragEventEnable = enable;
    }

    public void setMomentumScrollBeginEventEnable(boolean enable) {
        mMomentumScrollBeginEventEnable = enable;
    }

    public void setMomentumScrollEndEventEnable(boolean enable) {
        mMomentumScrollEndEventEnable = enable;
    }

    public void setOnScrollEventEnable(boolean enable) {
        mScrollEventEnable = enable;
    }

    protected HippyMap generateScrollEvent() {
        float value;
        HippyMap contentOffset = new HippyMap();
        if (mLayout.canScrollHorizontally()) {
            value = (mOffsetX - mState.mCustomHeaderWidth) / PixelUtil.getDensity();
            contentOffset.pushDouble("x", value);
            contentOffset.pushDouble("y", 0.0f);
        } else {
            value = (mOffsetY - mState.mCustomHeaderHeight) / PixelUtil.getDensity();
            contentOffset.pushDouble("x", 0.0f);
            contentOffset.pushDouble("y", value);
        }

        HippyMap event = new HippyMap();
        event.pushMap("contentOffset", contentOffset);
        return event;
    }

    public void setScrollEnable(boolean enable) {
        mScrollEnable = enable;
    }

    public void setExposureEventEnable(boolean enable) {
        mExposureEventEnable = enable;
    }

    public void setScrollEventThrottle(int scrollEventThrottle) {
        mScrollEventThrottle = scrollEventThrottle;
    }

    public View getCustomHeaderView() {
        if (getChildCount() > 0) {
            View firstChild = getChildAt(0);
            final ViewHolder holder = getChildViewHolderInt(firstChild);
            if (holder != null) {
                return holder.mContent;
            }
        }

        return null;
    }

    public View getCustomFooterView() {
        if (getChildCount() > 0) {
            View lastChild = getChildAt(getChildCount() - 1);
            final ViewHolder holder = getChildViewHolderInt(lastChild);
            if (holder != null) {
                return holder.mContent;
            }
        }

        return null;
    }

    public void onHeaderRefreshFinish() {
        if (mHeaderRefreshState == REFRESH_STATE_LOADING) {
            if (mLayout.canScrollHorizontally()) {
                if (mOffsetX < mState.mCustomHeaderWidth) {
                    smoothScrollBy(-mOffsetX + mState.mCustomHeaderWidth, 0, false, true);
                }
            } else {
                if (mOffsetY < mState.mCustomHeaderHeight) {
                    smoothScrollBy(0, -mOffsetY + mState.mCustomHeaderHeight, false, true);
                }
            }

            mHeaderRefreshState = REFRESH_STATE_IDLE;
        }
    }

    public void onFooterRefreshFinish() {
        if (mFooterRefreshState == REFRESH_STATE_LOADING) {
            if (mLayout.canScrollHorizontally()) {
                int contentOffsetX = getTotalHeight() - getWidth();
                if (mOffsetX > contentOffsetX) {
                    smoothScrollBy(contentOffsetX - mOffsetX, 0, false, true);
                }
            } else {
                int contentOffsetY = getTotalHeight() - getHeight();
                if (mOffsetY > contentOffsetY) {
                    smoothScrollBy(0, contentOffsetY - mOffsetY, false, true);
                }
            }
            mFooterRefreshState = REFRESH_STATE_IDLE;
        }
    }

    public void onHeaderRefresh() {
        if (mHeaderRefreshState == REFRESH_STATE_IDLE) {
            if (mLayout.canScrollHorizontally()) {
                smoothScrollBy(-mOffsetX, 0, false, true);
            } else {
                smoothScrollBy(0, -mOffsetY, false, true);
            }
        }
    }

    protected void onTouchMove(int x, int y) {
        int totalHeight = mAdapter.getTotalHeight();
        float contentOffset = 0;
        String eventName = "";
        if (mLayout.canScrollHorizontally()) {
            if (mOffsetX < mState.mCustomHeaderWidth) {
                contentOffset = Math.abs((mOffsetX - mState.mCustomHeaderWidth));
                eventName = EVENT_LIST_HEADER_PULLING;
            } else if (mOffsetX > totalHeight - getWidth()) {
                contentOffset = Math.abs((mOffsetX - totalHeight - getWidth()));
                eventName = EVENT_LIST_FOOTER_PULLING;
            }
        } else {
            if (getOffsetY() < mState.mCustomHeaderHeight) {
                contentOffset = Math.abs((getOffsetY() - mState.mCustomHeaderHeight));
                eventName = EVENT_LIST_HEADER_PULLING;
            } else if (getOffsetY() > totalHeight - getHeight()) {
                contentOffset = Math.abs((getOffsetY() - totalHeight - getHeight()));
                eventName = EVENT_LIST_FOOTER_PULLING;
            }
        }
        Map<String, Object> params = new HashMap<>();
        params.put("contentOffset", Double.valueOf(PixelUtil.px2dp(contentOffset)));
        if (eventName.equals(EVENT_LIST_HEADER_PULLING)) {
            sendPullHeaderEvent(eventName, params);
        } else if (eventName.equals(EVENT_LIST_FOOTER_PULLING)) {
            sendPullFooterEvent(eventName, params);
        }
    }

    private boolean shouldStopReleaseGlowsForHorizontal(boolean fromTouch) {
        int totalHeight = mAdapter.getTotalHeight();
        if (mOffsetX <= 0 || getWidth() > (totalHeight - mState.mCustomHeaderWidth)) {
            if (mHeaderRefreshState == REFRESH_STATE_IDLE && fromTouch) {
                sendPullHeaderEvent(EVENT_LIST_HEADER_RELEASED, null);
                mHeaderRefreshState = REFRESH_STATE_LOADING;
            }

            if (mOffsetX < 0) {
                smoothScrollBy(-mOffsetX, 0, false, true);
            }
            return true;
        } else {
            int refreshEnableOffsetX = totalHeight - getWidth() + mState.mCustomFooterWidth;
            if ((totalHeight - mState.mCustomHeaderWidth) < getWidth()
                    || mOffsetX >= refreshEnableOffsetX) {
                if (mFooterRefreshState == REFRESH_STATE_IDLE) {
                    sendPullFooterEvent(EVENT_LIST_FOOTER_RELEASED, null);
                    mFooterRefreshState = REFRESH_STATE_LOADING;
                }

                View footerView = getCustomFooterView();
                if (footerView instanceof HippyPullFooterView) {
                    boolean stickEnabled = ((HippyPullFooterView) footerView).getStickEnabled();
                    if (stickEnabled) {
                        smoothScrollBy(refreshEnableOffsetX - mOffsetX, 0, false, true);
                        return true;
                    }
                }
            }
        }

        return false;
    }

    private boolean shouldStopReleaseGlowsForVertical(boolean fromTouch) {
        int totalHeight = mAdapter.getTotalHeight();
        if (getOffsetY() <= 0 || getHeight() > (totalHeight - mState.mCustomHeaderHeight)) {
            if (mHeaderRefreshState == REFRESH_STATE_IDLE && fromTouch) {
                sendPullHeaderEvent(EVENT_LIST_HEADER_RELEASED, null);
                mHeaderRefreshState = REFRESH_STATE_LOADING;
            }
            if (getOffsetY() < 0) {
                smoothScrollBy(0, -mOffsetY, false, true);
            }
            return true;
        } else {
            int refreshEnableOffsetY = totalHeight - getHeight() + mState.mCustomFooterHeight;
            if ((totalHeight - mState.mCustomHeaderHeight) < getHeight()
                    || getOffsetY() >= refreshEnableOffsetY) {
                if (mFooterRefreshState == REFRESH_STATE_IDLE) {
                    sendPullFooterEvent(EVENT_LIST_FOOTER_RELEASED, null);
                    mFooterRefreshState = REFRESH_STATE_LOADING;
                }

                View footerView = getCustomFooterView();
                if (footerView instanceof HippyPullFooterView) {
                    boolean stickEnabled = ((HippyPullFooterView) footerView).getStickEnabled();
                    if (stickEnabled) {
                        smoothScrollBy(0, refreshEnableOffsetY - mOffsetY, false, true);
                        return true;
                    }
                }
            }
        }

        return false;
    }

    @Override
    protected boolean shouldStopReleaseGlows(boolean canGoRefresh, boolean fromTouch) {
        if (mEnableRefresh) {
//      Scroller scroller = mViewFlinger.getScroller();
//      if (scroller.isFinished() && scroller.isFling() && getOffsetY() < 0) {
//        canGoRefresh = true;
//      }

            if (!canGoRefresh) {
                return false;
            }

            if (mLayout.canScrollHorizontally()) {
                return shouldStopReleaseGlowsForHorizontal(fromTouch);
            } else {
                return shouldStopReleaseGlowsForVertical(fromTouch);
            }
        }

        return false;
    }

    @Override
    protected void onScrollDragStarted() {
        if (mScrollBeginDragEventEnable) {
            getOnScrollDragStartedEvent().send(this, generateScrollEvent());
        }
    }

    @Override
    protected void onScrollDragEnded() {
        if (mScrollEndDragEventEnable) {
            getOnScrollDragEndedEvent().send(this, generateScrollEvent());
        }
    }

    @Override
    protected void onScrollFlingStarted() {
        if (mMomentumScrollBeginEventEnable) {
            getOnScrollFlingStartedEvent().send(this, generateScrollEvent());
        }
    }

    @Override
    protected void onScrollFlingEnded() {
        if (mMomentumScrollEndEventEnable) {
            getOnScrollFlingEndedEvent().send(this, generateScrollEvent());
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
    }

    @Override
    public void onScrolled(int x, int y) {
        super.onScrolled(x, y);
        checkSendOnScrollEvent();
        if (mExposureEventEnable) {
            dispatchExposureEvent();
        }
    }

    @Override
    public void onScrollStateChanged(int oldState, int newState) {
        super.onScrollStateChanged(oldState, newState);
        if (mHasUnsentScrollEvent) {
            sendOnScrollEvent();
        }
    }

    protected void sendExposureEvent(@NonNull View view, @NonNull String eventName,
            @NonNull Map<String, Object> props) {
        if (props.containsKey(eventName)) {
            EventUtils.sendComponentEvent(view, eventName, null);
        }
    }

    private Map<String, Object> getItemViewProps(View view) {
        if (nativeRenderer == null) {
            return null;
        }
        RenderNode node = RenderManager.getRenderNode(view);
        if (node == null) {
            return null;
        }

        return node.getProps();
    }

    protected void checkExposureView(View view, int visibleStart, int visibleEnd, int parentStart,
            int parentEnd) {
        if (!(view instanceof HippyListItemView)) {
            return;
        }

        int myStart = (mLayout.canScrollHorizontally()) ? view.getLeft() : view.getTop();
        int myEnd = (mLayout.canScrollHorizontally()) ? view.getRight() : view.getBottom();
        myStart += parentStart;
        myEnd += parentStart;

        HippyListItemView itemView = (HippyListItemView) view;
        Map<String, Object> props = getItemViewProps(itemView);
        if (props == null) {
            return;
        }

        int currentExposureState = itemView.getExposureState();
        int viewSize = (mLayout.canScrollHorizontally()) ? view.getWidth() : view.getHeight();
        int correctingValueForDisappear = (int) Math.ceil(viewSize * 0.1f);

        if (myEnd <= (visibleStart + correctingValueForDisappear) || myStart >= (visibleEnd
                - correctingValueForDisappear)) {
            if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_INVISIBLE) {
                if (itemView.getExposureState() == HippyListItemView.EXPOSURE_STATE_FULL_VISIBLE) {
                    sendExposureEvent(view, EVENT_LIST_ITEM_WILL_DISAPPEAR, props);
                }
                sendExposureEvent(view, EVENT_LIST_ITEM_DISAPPEAR, props);
                itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_INVISIBLE);
            }
        } else if ((myStart < visibleStart && myEnd > visibleStart) || (myStart < visibleEnd
                && myEnd > visibleEnd)) {
            if (currentExposureState == HippyListItemView.EXPOSURE_STATE_FULL_VISIBLE) {
                sendExposureEvent(view, EVENT_LIST_ITEM_WILL_DISAPPEAR, props);
                itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_PART_VISIBLE);
            } else if (currentExposureState == HippyListItemView.EXPOSURE_STATE_INVISIBLE) {
                sendExposureEvent(view, EVENT_LIST_ITEM_WILL_APPEAR, props);
                itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_PART_VISIBLE);
            }
        } else if ((myStart >= visibleStart && myEnd <= visibleEnd) || (myStart <= visibleStart
                && myEnd > visibleEnd)) {
            if (itemView.getExposureState() != HippyListItemView.EXPOSURE_STATE_FULL_VISIBLE) {
                if (itemView.getExposureState() == HippyListItemView.EXPOSURE_STATE_INVISIBLE) {
                    sendExposureEvent(view, EVENT_LIST_ITEM_WILL_APPEAR, props);
                }
                sendExposureEvent(view, EVENT_LIST_ITEM_APPEAR, props);
                itemView.setExposureState(HippyListItemView.EXPOSURE_STATE_FULL_VISIBLE);
            }
        }
    }

    private void dispatchExposureEvent() {
        if (mLayout instanceof BaseLayoutManager) {
            BaseLayoutManager.OrientationHelper layoutHelper = ((BaseLayoutManager) mLayout).mOrientationHelper;
            if (layoutHelper == null) {
                return;
            }

            int count = getChildCount();
            int fixOffset = (mLayout.canScrollHorizontally()) ? mState.mCustomHeaderWidth
                    : mState.mCustomHeaderHeight;
            int start = layoutHelper.getStartAfterPadding() + fixOffset;
            int end = layoutHelper.getEndAfterPadding() - fixOffset;
            for (int i = 0; i < count; i++) {
                final View child = getChildAt(i);
                final int childStart = layoutHelper.getDecoratedStart(child);
                final int childEnd = layoutHelper.getDecoratedEnd(child);
                if (child instanceof RecyclerViewItem) {
                    RecyclerViewItem itemView = (RecyclerViewItem) child;
                    if (itemView.getChildCount() > 0) {
                        checkExposureView(itemView.getChildAt(0), start, end, childStart, childEnd);
                    }
                }
            }
        }
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        if (!mHasRemovePreDraw) {
            mViewTreeObserver = getViewTreeObserver();
            if (mPreDrawListener == null) {
                mPreDrawListener = new ViewTreeObserver.OnPreDrawListener() {
                    @Override
                    public boolean onPreDraw() {
                        if (mAdapter.getItemCount() > 0 && HippyListView.this.getChildCount() > 0) {
                            mViewTreeObserver.removeOnPreDrawListener(this);
                            mHasRemovePreDraw = true;

                            post(new Runnable() {
                                @Override
                                public void run() {
                                    HippyListView.this.onInitialListReady();
                                    getOnInitialListReadyEvent().send(HippyListView.this, null);
                                }
                            });

                        }
                        return true;
                    }
                };
            }
            mViewTreeObserver.removeOnPreDrawListener(mPreDrawListener);
            mViewTreeObserver.addOnPreDrawListener(mPreDrawListener);

        }
    }

    @Override
    protected void onDetachedFromWindow() {
        if (mPreDrawListener != null && mViewTreeObserver != null) {
            mViewTreeObserver.removeOnPreDrawListener(mPreDrawListener);
        }
        super.onDetachedFromWindow();
    }

    public void scrollToIndex(int xIndex, int yIndex, boolean animated, int duration) {
        if (animated) {
            int scrollToYPos = getHeightBefore(yIndex) - getOffsetY();
            if (duration != 0) //如果用户设置了duration
            {
                if (scrollToYPos != 0) {
                    if (!mState.didStructureChange()) {
                        mViewFlinger.smoothScrollBy(0, scrollToYPos, duration, true);
                    }
                }
            } else {
                smoothScrollBy(0, scrollToYPos);
            }
        } else {
            scrollToPosition(yIndex, 0);
            post(new Runnable() {
                @Override
                public void run() {
                    dispatchLayout();
                }
            });
        }
    }

    public void scrollToContentOffset(double xOffset, double yOffset, boolean animated,
            int duration) {
        int scrollToYPos = (int) PixelUtil.dp2px(yOffset) - mOffsetY;
        int scrollToXPos = (int) PixelUtil.dp2px(xOffset) - mOffsetX;
        if (animated) {
            if (duration != 0) {
                if ((scrollToYPos != 0 || scrollToXPos != 0) && !mState.didStructureChange()) {
                    mViewFlinger.smoothScrollBy(scrollToXPos, scrollToYPos, duration, true);
                }
            } else {
                smoothScrollBy(scrollToXPos, scrollToYPos);
            }
        } else {
            scrollBy(scrollToXPos, scrollToYPos);
            post(new Runnable() {
                @Override
                public void run() {
                    dispatchLayout();
                }
            });
        }
    }

    protected void checkSendOnScrollEvent() {
        if (mScrollEventEnable) {
            long currTime = SystemClock.elapsedRealtime();
            if (currTime - mLastScrollEventTimeStamp >= mScrollEventThrottle) {
                mLastScrollEventTimeStamp = currTime;
                sendOnScrollEvent();
            } else {
                mHasUnsentScrollEvent = true;
            }
        }
    }

    protected void sendOnScrollEvent() {
        mHasUnsentScrollEvent = false;
        getOnScrollEvent().send(this, generateScrollEvent());
    }

    // start drag event
    protected OnScrollDragStartedEvent getOnScrollDragStartedEvent() {
        if (mOnScrollDragStartedEvent == null) {
            mOnScrollDragStartedEvent = new OnScrollDragStartedEvent(
                    EventUtils.EVENT_SCROLLER_BEGIN_DRAG);
        }
        return mOnScrollDragStartedEvent;
    }

    protected static class OnScrollDragStartedEvent extends HippyViewEvent {

        public OnScrollDragStartedEvent(String eventName) {
            super(eventName);
        }
    }

    // end drag event
    protected OnScrollDragEndedEvent getOnScrollDragEndedEvent() {
        if (mOnScrollDragEndedEvent == null) {
            mOnScrollDragEndedEvent = new OnScrollDragEndedEvent(
                    EventUtils.EVENT_SCROLLER_END_DRAG);
        }
        return mOnScrollDragEndedEvent;
    }

    protected static class OnScrollDragEndedEvent extends HippyViewEvent {

        public OnScrollDragEndedEvent(String eventName) {
            super(eventName);
        }
    }

    // start fling
    protected OnScrollFlingStartedEvent getOnScrollFlingStartedEvent() {
        if (mOnScrollFlingStartedEvent == null) {
            mOnScrollFlingStartedEvent = new OnScrollFlingStartedEvent(
                    EventUtils.EVENT_SCROLLER_MOMENTUM_BEGIN);
        }
        return mOnScrollFlingStartedEvent;
    }

    protected static class OnScrollFlingStartedEvent extends HippyViewEvent {

        public OnScrollFlingStartedEvent(String eventName) {
            super(eventName);
        }
    }

    // end fling
    protected OnScrollFlingEndedEvent getOnScrollFlingEndedEvent() {
        if (mOnScrollFlingEndedEvent == null) {
            mOnScrollFlingEndedEvent = new OnScrollFlingEndedEvent(
                    EventUtils.EVENT_SCROLLER_MOMENTUM_END);
        }
        return mOnScrollFlingEndedEvent;
    }

    protected static class OnScrollFlingEndedEvent extends HippyViewEvent {

        public OnScrollFlingEndedEvent(String eventName) {
            super(eventName);
        }
    }

    // scroll
    protected OnScrollEvent getOnScrollEvent() {
        if (mOnScrollEvent == null) {
            mOnScrollEvent = new OnScrollEvent(EventUtils.EVENT_SCROLLER_ON_SCROLL);
        }
        return mOnScrollEvent;
    }

    protected static class OnScrollEvent extends HippyViewEvent {

        public OnScrollEvent(String eventName) {
            super(eventName);
        }
    }

    private OnInitialListReadyEvent getOnInitialListReadyEvent() {
        if (mOnInitialListReadyEvent == null) {
            mOnInitialListReadyEvent = new OnInitialListReadyEvent("initiallistready");
        }
        return mOnInitialListReadyEvent;
    }

    private static class OnInitialListReadyEvent extends HippyViewEvent {

        public OnInitialListReadyEvent(String eventName) {
            super(eventName);
        }
    }

    @SuppressWarnings("EmptyMethod")
    protected void onInitialListReady() {

    }

    protected static class PullElementEvent extends HippyViewEvent {

        public PullElementEvent(String eventName) {
            super(eventName);
        }
    }

    protected void sendPullHeaderEvent(@NonNull String eventName,
            @Nullable Map<String, Object> params) {
        View view = getCustomHeaderView();
        if (view instanceof HippyPullHeaderView) {
            EventUtils.sendComponentEvent(view, eventName, params);
        }
    }

    protected void sendPullFooterEvent(@NonNull String eventName,
            @Nullable Map<String, Object> params) {
        View view = getCustomFooterView();
        if (view instanceof HippyPullFooterView) {
            EventUtils.sendComponentEvent(view, eventName, params);
        }
    }
}
